package cmpe451.group7.ourcity.dao.impl;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

import org.apache.commons.collections4.CollectionUtils;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import cmpe451.group7.ourcity.dao.PlaceDAO;
import cmpe451.group7.ourcity.dao.TagDAO;
import cmpe451.group7.ourcity.entity.Place;
import cmpe451.group7.ourcity.entity.Tag;
import cmpe451.group7.ourcity.entity.User;
import cmpe451.group7.ourcity.util.Utils;

@Repository("placeDAO")
public class PlaceDAOImpl extends GenericDAOImpl<Place> implements PlaceDAO,
		Serializable {

	private static final long serialVersionUID = 1L;

	@Autowired
	private TagDAO tagDAO;

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public List<Place> findAll() {
		return super.findAll(Place.class);
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public Place findByID(long id) {
		return super.findByID(Place.class, id);
	}

	/**
	 * Add a new place to the database with the given parameters
	 */
	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public Place insertNewPlace(String placeName, String country, String city,
			int likeCount, double latitude, double longitude,
			Date creationDate, List<Tag> tags, User user) {

		Session session = this.getCurrentSession();
		Place place = new Place();
		place.setPlaceName(placeName);
		place.setCountry(country);
		place.setCity(city);
		place.setLikeCount(likeCount);
		place.setLatitude(latitude);
		place.setLongitude(longitude);
		place.setCreationDate(creationDate);
		if (user != null)
			place.setPlaceCreatorID(user.getUserID());

		if (tags != null) {
			for (Tag t : tags) {
				// check for existing tag in the database and add it if exists
				t = tagDAO.saveIfNotExists(t);
				if (t != null) {
					place.addTag(t);
				}
			}
		}

		session.save(place);
		// session.flush();

		return place;
	}

	/**
	 * Query the database with the given restrictions
	 */
	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public List<Place> findByRestrictions(String placeName, String country,
			String city, Date fromDate, Date toDate, List<Tag> tags) {

		return findByRestrictions(placeName, country, city, fromDate, toDate,
				tags, 0, 0, 0, 0);
	}

	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public Place incrementLikeCount(Place place, boolean liked) {
		Session session = this.getCurrentSession();

		int likeCount = place.getLikeCount();

		if (liked) {
			likeCount = likeCount + 1;
		}

		place.setLikeCount(likeCount);
		session.update(place);
		// session.flush();

		return place;
	}

	@Override
	public List<Place> findByRestrictions(String placeName, String country,
			String city, Date fromDate, Date toDate, List<Tag> tags,
			double ne_lat, double ne_lng, double sw_lat, double sw_lng) {

		Session session = this.getCurrentSession();

		boolean restrictPosition = !Utils.allZero(ne_lat, ne_lng, sw_lat,
				sw_lng);
		String restrictPositionQuery = " AND (latitude <= :ne_lat AND latitude >= :sw_lat AND "
				+ "longitude <= :ne_lng AND longitude >= :sw_lng) ";

		boolean useTags = CollectionUtils.isNotEmpty(tags);

		String hql = "SELECT p " + " FROM Place p "
				+ (useTags ? "JOIN p.tags tags, TagSimilarity ts " : "")
				+ " WHERE " + " p.placeName LIKE :pn AND "
				+ " p.country LIKE :cn AND" + " p.city LIKE :ct "
				+ ((fromDate != null) ? " AND creationDate >= :fd " : "")
				+ ((toDate != null) ? " AND creationDate <= :td " : "")
				+ (restrictPosition ? restrictPositionQuery : "");

		if (useTags) {
			hql += " AND ts.tag1_mID IN (:mid_list) AND "
					+ " EXISTS (SELECT t FROM Tag t WHERE ts.tag2_mID = tags.mID ) "
					+ " GROUP BY p.placeID HAVING avg(ts.similarity) > 0 "
					+ " ORDER BY avg(ts.similarity) desc";
		} else {
			hql += " GROUP BY p.placeID ";
		}

		Query query = session.createQuery(hql);
		query.setParameter("pn", placeName + "%");
		query.setParameter("cn", country + "%");
		query.setParameter("ct", city + "%");

		if (fromDate != null)
			query.setParameter("fd", fromDate);
		if (toDate != null)
			query.setParameter("td", toDate);

		List<String> mids = null;
		if (useTags) {
			mids = Utils.extractMIDs(tags);
			query.setParameterList("mid_list", mids);
		}

		if (restrictPosition) {
			query.setParameter("ne_lat", ne_lat);
			query.setParameter("ne_lng", ne_lng);
			query.setParameter("sw_lat", sw_lat);
			query.setParameter("sw_lng", sw_lng);
		}

		@SuppressWarnings("unchecked")
		List<Place> places = query.list();

		return places;
	}

	@Override
	@Transactional
	public List<Place> getRelatedPlaces(List<Tag> tags) {
		Session session = getCurrentSession();

		List<String> mids = Utils.extractMIDs(tags);

		String hql = "SELECT p "
				+ "FROM Place p JOIN p.tags tags, TagSimilarity ts "
				+ "WHERE ts.tag1_mID IN (:mid_list) AND "
				+ "EXISTS (SELECT t FROM Tag t WHERE ts.tag2_mID = tags.mID ) "
				+ "GROUP BY p.placeID HAVING avg(ts.similarity) > 0 "
				+ "ORDER BY avg(ts.similarity) desc";

		Query query = session.createQuery(hql);
		query.setParameterList("mid_list", mids);

		@SuppressWarnings("unchecked")
		List<Place> list = query.list();
		return list;
	}

	@Override
	@Transactional
	public int getNumberOfUserPlaces(User user) {
		int number = 0;
		Session session = this.getCurrentSession();

		String hql = "select a from Place a " + "where "
				+ "placeCreatorID like :us " + "group by a";

		Query query = session.createQuery(hql);
		query.setParameter("us", user.getUserID());

		@SuppressWarnings("unchecked")
		List<Place> places = query.list();

		number = places.size();

		return number;
	}
}
